package com.zkc.zblog.Shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import cn.hutool.core.codec.Base64;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import org.apache.shiro.mgt.SecurityManager;
@Configuration
public class ShiroConfig {


    private final String CACHE_KEY = "shiro:cache:"; //redis中 cache 的key
    private final String SESSION_KEY = "shiro:session:";  //redis中 session 的key

    private final int EXPIRE_cache= 60*60*24;   //cache  在redis 中缓存的时间

    private final int EXPIRE_session= 60*60*24;  //session  在 redis中缓存的时间


    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;






    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //添加Shrio内置过滤器
        /**
         *        Shiro内置过滤器，可以实现权限相关的拦截器
         *        常用的过滤器:
         *        anon:无需认证（登录）可以访问
         *        authc:必须认证才可以访问
         *        user:如果使用rememberMe的功能可以直接访问
         *        perms:该资源必须得到资源权限才可以访问
         *        role:该资源必须得到角色权限才可以访问
         */
        LinkedHashMap<String ,String> filterMap = new LinkedHashMap<>();
        /*静态资源*/
        filterMap.put("/css/**","anon");
        filterMap.put("/indexs/**","anon");
        filterMap.put("/font/**","anon");
        filterMap.put("/images/**","anon");
        filterMap.put("/imgs/**","anon");
        filterMap.put("/lay/**","anon");
        filterMap.put("/login/**","anon");
        filterMap.put("/video/**","anon");
        filterMap.put("/layui.all.js","anon");
        filterMap.put("/layui.js","anon");
        filterMap.put("/UserAll/**","anon");
        filterMap.put("/utf8-jsp/**","anon");
        filterMap.put("/article/**","anon");
        filterMap.put("/ueditor/**","anon");



        /*开发暂时放行*/
//        filterMap.put("/index","anon");  //进入后台首页
//        filterMap.put("/nav/**","anon"); //分类页面
//        filterMap.put("/zblogUser/UserAll","anon"); //后台管理员的全部查询
//        filterMap.put("/upload","anon");  //单个图片上传接口
//        filterMap.put("/zblogimg/**","anon");   //图片访问路径
//        filterMap.put("/zblogArticle/**","anon");   //文章curd接口
//        filterMap.put("/config","anon");   //百度富文本编辑器上传图片前的接口  假的上传图片接口
//        filterMap.put("/config2","anon");   //  实际的上传图片接口

          filterMap.put("/timing/**","anon");  //定时任务接口

        /*后台路径*/
        filterMap.put("/captcha/captchaImage**", "anon");//图片路径 验证码
        filterMap.put("/zblogUser/admin","anon");//后台登录
        filterMap.put("/userlogo", "anon");//后台注册页面
        filterMap.put("/zblogUser/registered","anon"); //后台注册发送邮件验证码
        filterMap.put("/zblogUser/registeredcode","anon"); //后台注册数据入库
        filterMap.put("/admin","anon"); //后台登录url
        filterMap.put("/noauth","anon"); //后台登录失败页面
        filterMap.put("/**","authc"); //所有的路径都拦截   除了上面的  此代码必须写在最后

        shiroFilterFactoryBean.setLoginUrl("/"); //默认访问路径
        shiroFilterFactoryBean.setSuccessUrl("/index");   //登录成功的url
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");  //登录失败访问的页面
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }





    /**
    *创建DefaultWebSecurityManager
    * 主要是在DefaultWebSecurityManager中需要配置对应的sessionManager和CacheManager即可.
    * 配置好之后，当登录完成，会在Redis中看到对应的key.而且此时的缓存也交给了redis来处理了
    */
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm){
        //@Qualifier("userRealm")将userRealm注入当前类
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm); //往SecurityManager中注入Realm,代替原本的默认配置
        //将缓存注入安全管理器，就不会反复执行  realm的授权方法了；只要实现了shiro的cache接口、CacheManager接口就可以用来注入安全管理器
        //shiro自带的一个内存缓存，本质是hashmap，MemoryConstrainedCacheManager()，试验没问题，非常轻，简单的登录用这个
        securityManager.setCacheManager(redisCacheManager());
        // 自定义session管理 使用redis，nigix试验分布式，确实 做到了session共享
        securityManager.setSessionManager(redisSessionManager());
        //注入记住我cookie管理器;
        securityManager.setRememberMeManager(rememberMeManager());


        return securityManager;
    }








    /******************************************************************redis缓存实现session共享**************************************************************************************/
    /**
     * 配置shiro redisManager
     * 网上的一个 shiro-redis 插件，实现了shiro的cache接口、CacheManager接口就
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host+":"+port);
        redisManager.setPassword(password); //redis 密码
        redisManager.setTimeout(timeout);
        return redisManager;
    }
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        redisCacheManager.setKeyPrefix(CACHE_KEY);  //设置 redis cache的key
        //redisCacheManager.setExpire(EXPIRE_cache);   //设置 cache 缓存的时间
        redisCacheManager.setPrincipalIdFieldName("uid");   //绑定用户表的id   大概意思是 吧shiro+redis生成的session id指向一个字段上面  这里 作者 只想用户表的id上面
        return redisCacheManager;
    }
    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        redisSessionDAO.setKeyPrefix(SESSION_KEY);  //设置redis session的key
        //redisSessionDAO.setExpire(EXPIRE_session);   //设置 session 缓存的时间
        return redisSessionDAO;
    }
    /**
     * shiro session的管理
     */
    @Bean
    public DefaultWebSessionManager redisSessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //使用redis缓存session
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setCacheManager(redisCacheManager());
        //全局会话超时时间（单位毫秒），默认30分钟  暂时设置为10秒钟 用来测试
        sessionManager.setGlobalSessionTimeout(3600000L);
        //是否开启删除无效的session对象  默认为true
        sessionManager.setDeleteInvalidSessions(true);
        //取消url 后面的 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        //是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        //暂时设置为 5秒 用来测试
        sessionManager.setSessionValidationInterval(3600000);
        return sessionManager;
    }



    /************************************************************************************redis缓存实现session共享**************************************************************************************/


    /**===================================================================================开启cookie的记住我===================================================================================*/
     /**
     * cookie对象;
     * @return
     */
    public SimpleCookie rememberMeCookie(){
        //这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }
     /**
     * cookie管理对象;记住我功能
     * @return
     */
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }
    /**===================================================================================开启cookie的记住我===================================================================================*/




    /*
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm(){
        return new UserRealm();
    }

    /*
     * 配置shiroDialect,用于thymeleaf和shiro标签配合使用
     */
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

    /**
     *  开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
     *  需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /**
     * 开启aop注解支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


}
